package com.ihr360.payroll.service.salarytasklet.step.calculator;

import com.ihr360.alps.api.ServiceProvider;
import com.ihr360.attendance.AttendancePeriodReport;
import com.ihr360.common.DateRange;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.meta.table.DynamicCell;
import com.ihr360.commons.model.type.EnrollAdjustFormula;
import com.ihr360.commons.model.type.FieldSource;
import com.ihr360.commons.model.type.FormulaFrom;
import com.ihr360.commons.model.type.RoundingMode;
import com.ihr360.commons.model.type.SalaryFieldValueType;
import com.ihr360.expr.ExprArray;
import com.ihr360.expr.ExprDouble;
import com.ihr360.expr.ExprError;
import com.ihr360.expr.ExprException;
import com.ihr360.expr.ExprInteger;
import com.ihr360.expr.ExprString;
import com.ihr360.expr.core.Expr;
import com.ihr360.expr.core.ExprVariable;
import com.ihr360.expr.data.ExprValueMeta;
import com.ihr360.expr.engine.DependencyEngine;
import com.ihr360.expr.engine.EngineProvider;
import com.ihr360.expr.engine.VariableFetcher;
import com.ihr360.expr.engine.VariableID;
import com.ihr360.expr.parser.ExprToken;
import com.ihr360.expr.utils.ExprUtils;
import com.ihr360.job.core.entity.StepExecutionLog;
import com.ihr360.payroll.calc.StaffSalaryCalcPeriod;
import com.ihr360.payroll.calc.StaffSalaryCalcPeriodTerm;
import com.ihr360.payroll.model.dto.salary.basecode.SalaryFieldType;
import com.ihr360.payroll.model.dto.salary.embeddable.SalaryFieldValue;
import com.ihr360.payroll.model.dto.salary.meta.SalaryField;
import com.ihr360.payroll.model.dto.salary.meta.SalaryFieldCode;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalarySnapshot;
import com.ihr360.payroll.model.type.SalaryPhase;
import com.ihr360.payroll.service.salarytasklet.SalaryCalcJobConfig;
import com.ihr360.payroll.service.salarytasklet.expr.engine.PersonVariableProvider;
import com.ihr360.payroll.service.salarytasklet.expr.engine.SalaryDependencyEngine;
import com.ihr360.payroll.service.salarytasklet.step.SalaryCalcContext;
import com.ihr360.sharedservice.model.WorkingCalendar;
import com.ihr360.validation.ValidationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author davidwei
 *         员工薪资计算核心逻辑
 */
public class PersonalSalaryCalculator {

    private final static String QUIT_OUT_FORMULA="=IF(DAY(STAFF_ROSTER$leaveDate)<15,$SAL_ED_ADJUST_RULE$calcBase*ATT_M_R$paiddays/$SAL_ED_ADJUST_RULE$monthWorkCount-$SAL_ED_ADJUST_RULE$calcBase,-$SAL_ED_ADJUST_RULE$calcBase*ATT_M_R$nonWorkingDays/$SAL_ED_ADJUST_RULE$monthWorkCount)";
    public static int CalcCount = 1;
    final Logger logger = LoggerFactory.getLogger(PersonalSalaryCalculator.class.getSimpleName());
    private static final String STEP_NAME = "[薪资核算]";
    private SalaryCalcContext salaryCalcContext;

    private SalaryDependencyEngine engine;

    private final PersonVariableProvider personVariableProvider = new PersonVariableProvider();

    private final PersonalCalcLog personalCalcLog = new PersonalCalcLog();

    private IPersonalCalcultorEvent personalCalcultorEvent;

    private Map<String, SalaryField> salaryFieldMap;

    private Map<String, SalaryField> attendanceSalaryFieldMap;

    /**
     * 保存输入类型的薪资项目取值
     */
    private Map<String, DynamicCell> inputData;

    /**
     * 保存引用类型(外部服务)的薪资项目取值
     */

    private Map<String, Map<String, Object>> referenceData;

    /**
     * 保存员工的分段薪资薪资
     */
    private StaffSalaryCalcPeriod staffPeriodSalary;

    /**
     * 员工月度考勤报告(在多批次计算的时候，这个报告其实是批次区间内的报告，不是一个月的)
     */
    private AttendancePeriodReport attendanceMonthlyReport;

    /**
     * 员工分段考勤数据
     */
    private List<AttendancePeriodReport> attendancePeriodReports;

    /**
     * 合并计税条件下的数据
     */
    private StaffSalarySnapshot basedOnTermSalary;

    /**
     * 工作日历
     */

    private WorkingCalendar workingCalendar;

    private boolean isSamePeriod = true;

    /**
     * 是否只要一個階段
     */
    private boolean isSinglePhase = true;

    /**
     * 是否是單批次計算
     */
    private boolean isSingleTerm = true;

    /**
     * 税表
     */
    Map<String, ExprArray> taxTables;

    private DateRange payrollPriod;

    private DateRange attendancePeriod;

    /**
     * 考勤周期内的法定计薪天数
     */
    private double attendanceLegalPaidDayInThisMonth;

    /**
     * 入离职调整算法
     */
    public EnrollAdjustFormula enrollAdjustFormula;

    /**
     * 是否需要进行调薪调整
     */
    private boolean needPhaseAdjustment = false;

    /**
     * 是否启用病假分段计薪
     */
    private boolean sickLeavePhasedCalcEnable;

    /**
     * 是否启用事假分段计薪
     */
    private boolean personalLeavePhasedCalcEnable;


    /**
     * 调薪调整是否开启
     */
    private boolean salaryAdjustPhasedCalcEnable;


    private Set<String> profileFields;

    private String staffId;
    private String staffName;
    /**
     * 本月入职
     */
    private boolean enrollIn;
    /**
     * 本月离职
     */
    private boolean quitOut;

    public void initContext(SalaryCalcContext salaryCalcContext) {
        Assert.notNull(this.payrollPriod, "薪资周期不能为空");
        Assert.notNull(this.attendancePeriod, "考勤周期不能为空");
        if (this.salaryCalcContext != null) {
            return;
        }
        this.salaryCalcContext = salaryCalcContext;
        //分段获取员工薪资部分 和 分段获取计算考勤薪资部分
        this.engine = salaryCalcContext.dependencyEngineHolder.getWrittenItem();

        //准备开始计算一个人的薪资
        EngineProvider provider = engine.getProvider();
        //保存用户变量

        salaryFieldMap = salaryCalcContext.salaryFieldsHolder.getWrittenItem();
        attendanceSalaryFieldMap = new HashMap<>(20);
        for (Map.Entry<String, SalaryField> entry : salaryFieldMap.entrySet()) {
            SalaryField value = entry.getValue();
            SalaryFieldType type = value.getType();
            if (type != null) {
                //将考勤部分数据加入分段你数据中
                if (SalaryFieldType.Code.attendance.equals(type.getCode())) {
                    attendanceSalaryFieldMap.put(entry.getKey(), entry.getValue());
                }
            }
        }


        provider.setVariableFetcher(new VariableFetcher() {
            @Override
            public Expr variable(String variableId) {
                return personVariableProvider.getVariable(variableId);
            }

            @Override
            public Expr notifyMissing(ExprVariable variable) {

                String name = variable.getName();
                logger.debug("[MISSing]" + name + " 0 as default value");
                return ExprInteger.ZERO;
                //return ExprMissing.MISSING;
            }

            @Override
            public ExprValueMeta valueMeta(String variableId) {
                SalaryField field = salaryFieldMap.get(variableId);
                if (field != null) {
                    SalaryFieldValue valueMeta = field.getValueMeta();
                    if (valueMeta != null) {
                        int sacle = valueMeta.getPattern();
                        RoundingMode roundingMode = valueMeta.getRoundingMode();
                        if (roundingMode != null && SalaryFieldValueType.NUMBER.equals(valueMeta.getValueType())) {
                            return ExprValueMeta.buildValueMeta(roundingMode.getJavaRoundingMode(), sacle);
                        }
                    }
                }
                return null;
            }

            @Override
            public void onError(VariableID variable, Expr input, ExprError e, double defaultValue) {
                String variableId = variable.getVariableId();
                SalaryField field = salaryFieldMap.get(variable.getVariableId());
                String variableName;
                if (field != null) {
                    variableName = field.getName();
                } else {
                    variableName = salaryCalcContext.getSystemSalaryFieldName(variableId);
                }
                personalCalcLog.addLog(StepExecutionLog.LogLevel.INFO, STEP_NAME, String.format("%s(%s) 计算失败。设置为默认值%s", variableName, variableId, defaultValue));
                personalCalcLog.addLog(StepExecutionLog.LogLevel.TRACE, STEP_NAME, String.format("%s(%s) 计算失败 INPUT:%s ERROR: %s", variableName, variableId, input.toString(), e.getMessage()));
            }
        });

        workingCalendar = salaryCalcContext.workingCalendarHolder.getWrittenItem();
        ValidationUtils.rejectIfEmpty(workingCalendar, null, "无法获取工作日历");

        //attendanceLegalPaidDayInThisMonth = workingCalendar.getLegalPaidDayOfThisMonth(attendancePeriod.getStart(), attendancePeriod.getEnd());
        attendanceLegalPaidDayInThisMonth = salaryCalcContext.monthlyEDStandardWorkingDays;
        this.sickLeavePhasedCalcEnable = salaryCalcContext.sickLeavePhasedCalcEnable;
        this.personalLeavePhasedCalcEnable = salaryCalcContext.personalLeavePhasedCalcEnable;
        this.salaryAdjustPhasedCalcEnable = salaryCalcContext.salaryAdjustPhasedCalcEnable;
        this.enrollAdjustFormula = salaryCalcContext.enrollAdjustFormula;
    }


    public void setPersonalCalcultorEvent(IPersonalCalcultorEvent personalCalcultorEvent) {
        this.personalCalcultorEvent = personalCalcultorEvent;
    }

    public IPersonalCalcultorEvent getPersonalCalcultorEvent() {
        return personalCalcultorEvent;
    }

    public void resetContext(SalaryCalcContext salaryCalcContext) {
        this.salaryCalcContext = null;
        this.needPhaseAdjustment = false;
        initContext(salaryCalcContext);
    }

    public void clearContext() {
        this.needPhaseAdjustment = false;
        this.salaryCalcContext = null;
        this.personVariableProvider.clear();
        if (this.taxTables != null) {
            this.taxTables.clear();
            this.taxTables = null;
        }
        if (this.inputData != null) {
            this.inputData = null;
        }
        if (this.referenceData != null) {
            this.referenceData = null;
        }
        if (this.staffPeriodSalary != null) {
            this.staffPeriodSalary = null;
        }
        if (this.attendancePeriodReports != null) {
            this.attendancePeriodReports = null;
        }
        if (this.attendanceMonthlyReport != null) {
            this.attendanceMonthlyReport = null;
        }
    }

    public void setEnrollIn(boolean enrollIn) {
        this.enrollIn = enrollIn;
    }

    public void setQuitOut(boolean quitOut) {
        this.quitOut = quitOut;
    }

    public void setTaxTables(Map<String, ExprArray> taxtables) {
        this.taxTables = taxtables;
    }

    public void setInputData(Map<String, DynamicCell> inputData) {
        this.inputData = inputData;
    }

    public void setReferenceData(Map<String, Map<String, Object>> staffReferenceData) {
        this.referenceData = staffReferenceData;
    }

    public void setPeriodSalaryProfile(StaffSalaryCalcPeriod salaryProfile) {
        this.staffPeriodSalary = salaryProfile;
    }

    public void setAttendanceMonthlyReport(AttendancePeriodReport attendanceMonthlyReport) {
        this.attendanceMonthlyReport = attendanceMonthlyReport;
    }

    public void setAttendancePeriodReports(List<AttendancePeriodReport> attendancePeriodReports) {
        this.attendancePeriodReports = attendancePeriodReports;
    }

    public void setBasedOnTermSalary(StaffSalarySnapshot basedOnTermSalary) {
        this.basedOnTermSalary = basedOnTermSalary;
    }

    public void setIsSinglePhase(boolean isSinglePhase) {
        this.isSinglePhase = isSinglePhase;
    }

    public void setIsSingleTerm(boolean isSingleTerm) {
        this.isSingleTerm = isSingleTerm;
    }

    public void setPayRollPeriod(DateRange dateRange) {
        this.payrollPriod = dateRange;
    }

    public void setAttendancePeriod(DateRange dateRange) {
        this.attendancePeriod = dateRange;
    }

    public void setProfileFields(Set<String> profileFields) {
        this.profileFields = profileFields;
    }

    public void setStaffId(String staffId) {
        this.staffId = staffId;
    }

    public void setStaffName(String staffName) {
        this.staffName = staffName;
    }

    public PersonalCalcLog personalCalcLog() {
        return personalCalcLog;
    }


    public StaffSalaryCalcPeriod processSinglePhased() {
        return this.process();
    }

    private void addVariables2Provider(String serviceId, Map<String, DynamicCell> cells, boolean ignoreNULL) {
        if (CollectionUtils.isNotEmpty(cells)) {
            for (Map.Entry<String, DynamicCell> field : cells.entrySet()) {
                String filedName = field.getKey();
                DynamicCell cell = field.getValue();
                if (ignoreNULL) {
                    if (cell != null && StringUtils.isNotEmpty(cell.getCellValue())) {
                        String variableID = VariableID.buildVariableId(serviceId, filedName);
                        addCellVariableConvertor(cell, variableID);
                    }
                } else {
                    String variableID = VariableID.buildVariableId(serviceId, filedName);
                    addCellVariableConvertor(cell, variableID);
                }
            }
        }
    }


    /**
     * 将加入的变量按照设定的格式进行转换
     *
     * @param cell
     * @param variableID
     */
    private void addCellVariableConvertor(DynamicCell cell, String variableID) {
        if (cell != null && cell.getCellValue()!=null && cell.getCellValue().startsWith(ExprToken.EQUAL.val)) {
            try {
                engine.set(variableID, cell.getCellValue());
            } catch (ExprException e) {
                logger.error(e.getMessage(), e);
                personalCalcLog.addLog(StepExecutionLog.LogLevel.TRACE, STEP_NAME, String.format("VARIABLE: %s ERROR: %s", variableID, e.getMessage()));
                //TODO: log exception
            }
        } else {
            if (cell == null || cell.getCellValue()==null) {
                personVariableProvider.removeVaribale(variableID);
                engine.getProvider().removeVariable(variableID);
            } else {
                personVariableProvider.addVaribale(variableID, ExprUtils.parseValueToDoubleExpr(cell.getCellValue()));
            }
        }
    }


    public void printCalcResult(DateRange range, DependencyEngine engine, Collection<SalaryField> salaryFields) {
        logger.info("----------------------------------------------------------");
        logger.info("begin: {}", range.getStart() != null ? range.getStart().toString() : "");
        logger.info("end: {}", range.getEnd() != null ? range.getEnd().toString() : "");

        salaryFields.stream().filter(s -> engine.getValueAsString(s.toVariableId()) != null).forEach(f -> {
            SalaryFieldValue valueMeta = f.getValueMeta();
            String value = engine.getValueAsString(f.toVariableId());
            logger.info("--{}({}:{})={} ", f.getName(), f.getCode(), value, valueMeta != null ? valueMeta.getFormula() : "");
        });


        salaryFields.stream().filter(s -> engine.getValueAsString(s.toVariableId()) == null).forEach(f -> {
            SalaryFieldValue valueMeta = f.getValueMeta();
            logger.error("--{}({}={}) ", f.getName(), f.getCode(), valueMeta != null ? valueMeta.getFormula() : "");
        });

        logger.info("----------------------------------------------------------");
    }

    /**
     * 单批次下的分段计算大致分为两种情况
     * 1. 员工基本薪资档案部分的分段计算。分段计算后的结果作为 汇总计算的输入项目
     * 2. 考勤部分(加班工资,病假扣款，实际扣款等) 分段计算后的结果作为汇总计算的输入项目
     * <p>
     * 考勤周期和薪资周期不一致
     * case1
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +       (转正)
     * +------A--+---B---1-------------C-------------------------------31
     * (加班)             (加班)
     * +26------28-------1------------------------------------25
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * <p>
     * * case2
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +                              (转正)
     * +----------A-----1--------B------+-----------C------------------31
     * (加班)             (加班)           (加班)
     * +26--------------1------------------------------------25
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * <p>
     * 考勤周期和薪资周期一致
     * case1
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +                   (转正)
     * +
     * +1----------A-------—+-------------B------------31+
     * (加班)                (加班)
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     */
    public StaffSalaryCalcPeriod processMultiplePhased() {
        if (isSamePeriod) {
            return process();
        } else {
            return processMultiplePhasesWithDifferentPeriod();
        }
    }


    /**
     * 考勤周期和薪资周期一致
     * case1
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +                   (转正)
     * +
     * +1----------A-------—+-------------B------------31+
     * (加班)                (加班)
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * 此种情况只需要讲 薪资基础部分按比例合计，考勤部分分段计算后合计
     * <p>
     * 在无法获取分段考勤数据的条件下，员工的考勤相关的工资项目计算采用当前工资计算。
     */
    private StaffSalaryCalcPeriod process() {
        personalCalcLog.clear();
        CalcCount++;
        ValidationUtils.rejectIfEmpty(staffPeriodSalary, null, "员工薪资档案薪资为空");
        ValidationUtils.rejectIfEmpty(payrollPriod, null, "薪资计算周期未设定");
        ValidationUtils.rejectIfEmpty(attendancePeriod, null, "薪资考勤周期未设定");
        ValidationUtils.rejectIfEmpty(taxTables, null, "税表未设定");

        //设置税表信息
        addTaxTableToProvider();

        //不管分布不分段，输入数据只有一份
        addInputDataToProvider();

        //不管分不分段，员工的服务引用数据只有一份
        addReferenceDataToProvider();

        //不管分不分段都设置合并计税数据部分
        addBasedOnTermSalaryToProvider();

        isSinglePhase = staffPeriodSalary.isSinglePhase();

        boolean hasPhasedAttendanceReport = true;

        if (isSinglePhase) {
            hasPhasedAttendanceReport = false;
        }

        if (CollectionUtils.isEmpty(attendancePeriodReports)) {
            hasPhasedAttendanceReport = false;
        }

        if (isSinglePhase) {
            needPhaseAdjustment = false;
            calculateSinglePhase(hasPhasedAttendanceReport);
        } else {
            if (staffPeriodSalary.getPhaseCount() > 1) {
                List<StaffSalaryCalcPeriodTerm> phases = staffPeriodSalary.sortedStaffSalaryCalcPeriodTerms();
                if (this.salaryAdjustPhasedCalcEnable) {
                    try {
                        processPhaseAdjustmentVariable(phases);
                    } catch (Exception e) {
                        if (logger.isErrorEnabled()) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                int phase = 0;
                for (StaffSalaryCalcPeriodTerm term : phases) {
                    //开始设置薪资分段计算基础部分
                    String phaseName = getPhaseName(isSinglePhase, phase);
                    logger.info("-Phase: {} index {}", phaseName, phase);

                    Map<String, DynamicCell> salaryProfile = addProfileToProvider(term, true);

                    //开始设置薪资分段计算考勤部分

                    if (!hasPhasedAttendanceReport) {
                        //step2 设置考勤月报
                        if (attendanceMonthlyReport != null) {
                            Map<String, Double> attendanceData = attendanceMonthlyReport.toDynamicCells();
                            addAttendanceReportToProvider(null, attendanceData, false);
                        }
                    } else {
                        //设置分段数据，如果分段数据没有，那么不需要分段计算考勤薪资部分
                        //设置当前分段的考勤数据,如果没有结束日期，默认结束时间为考勤周期结束时间
                        DateRange range = DateRange.build(term.getStartOn(), term.getEndOn() != null ? term.getEndOn() : attendancePeriod.getEnd());
                        /**
                         * | 标记的位置为分段点,1-31为薪资周期，26-25为考勤周期
                         *              1---P3----|-------------P2---------------|-------P1---------31
                         *   26-----------P3------|-------------P2---------------|---P1----25
                         *
                         *              1---P2---|------------------------------P1-------------------31
                         *   26----------P2------|------------------------------P1----------25
                         *
                         *  ---P2---|---1---------------------------------------P1-------------------31
                         *   26-----|-------------------------------------------P1---------25
                         */
                        //phases里面分段开始时间，是以考勤周期为基础的，这里的分段数据主要用于考勤相关薪资的分段计算。
                        Optional<AttendancePeriodReport> optional = attendancePeriodReports.stream().filter(s -> s.getDateRange().isSameSartDate(range)).findFirst();
                        if (optional.isPresent()) {
                            AttendancePeriodReport periodReport = optional.get();
                            Map<String, Double> periodAttendanceData = periodReport.toDynamicCells();
                            //保存分段考勤数据
                            addAttendanceReportToProvider(phaseName, periodAttendanceData, true);
                        } else {
                            logger.warn("未{}找到分段信息");
                        }
                    }
                    try {
                        engine.setAutoCalculate(true);
                        engine.calculate(true);
                    } catch (Exception e) {
                        personalCalcLog.addLog(StepExecutionLog.LogLevel.WARNING, STEP_NAME, e.getMessage() != null ? e.getMessage() : "计算异常");
                        personalCalcLog.addLog(StepExecutionLog.LogLevel.TRACE, STEP_NAME, e.getMessage() != null ? e.getMessage() : "计算异常," + e.getClass().getName());
                        logger.error(e.getMessage(), e);
                    }

                    //分段计薪
                    List<StaffSalaryCalcPeriodTerm> phasesData = staffPeriodSalary.getPhases();
                    StaffSalaryCalcPeriodTerm phaseData = new StaffSalaryCalcPeriodTerm();
                    BeanUtils.copyProperties(term, phaseData);
                    if (CollectionUtils.isEmpty(phasesData) || phase > phasesData.size()) {
                        phaseData = new StaffSalaryCalcPeriodTerm();
                    }
                    //设置阶段起始时间
                    ValidationUtils.rejectIfEmpty(term.getStartOn(), null, "阶段起始时间不能为空");
                    ValidationUtils.rejectIfEmpty(term.getEndOn(), null, "阶段结束时间不能为空");
                    phaseData.setStartOn(term.getStartOn());
                    phaseData.setEndOn(term.getEndOn() != null ? term.getEndOn() : payrollPriod.getEnd());

                    //将需要保存的分段数据写入分段结果（只有分段基本薪酬，分段考勤数据和考勤薪资写入结果，其他项目不用写入）
                    //写入基本薪酬
                    //只保存薪资单中使用到的工资
                    engine.setAutoCalculate(false);

                    if (CollectionUtils.isNotEmpty(salaryProfile)) {
                        processProfileSalary(isSinglePhase, salaryProfile, phaseName, phaseData);
                    } else {
                        personalCalcLog.addLog(StepExecutionLog.LogLevel.WARNING, STEP_NAME, "无法获取薪资档案信息");
                    }
                    //保存分段考勤数据,只保存薪资单中使用到的

                    //保存考勤相关的薪资（加班工资，病假扣款，事假扣款，入离职调整），只保存薪资单中使用到的
                    //只有当有分段考勤数据是，才需要保存分段的计算结果。
                    if (hasPhasedAttendanceReport) {
                        processAttendanceSalary(phaseName, phaseData);
                    }

                    printCalcResult(term, salaryProfile);
                    phase++;
                }
            }

            calculateSinglePhase(hasPhasedAttendanceReport);
        }

        if (personalCalcultorEvent != null) {
            personalCalcultorEvent.onCompleted(staffId, staffName, personVariableProvider, engine);
        }
        //处理薪资档案相关的引用数据
        Map<String, SalaryField> salaryFields = salaryCalcContext.salaryFieldsHolder.getWrittenItem();

        printCalcResult(this.payrollPriod, engine, salaryFields.values());
        logger.info("CalcCount=" + CalcCount);
        //清空当前人的信息
        personVariableProvider.clear();
        this.enrollIn = false;
        this.quitOut = false;

        return staffPeriodSalary;
    }

    private void calculateSinglePhase(boolean hasPhasedAttendanceReport) {
        try {
            engine.setAutoCalculate(false);
            //开始做汇总计算，重新设置变量
            //step 1. 设置薪资基础部分(阶段1的数据，也就是距离薪资计算周期最近有效数据)
            addLastestPeriodToProvider();
            //step2 设置考勤月报
            if (attendanceMonthlyReport != null) {
                addAttendanceReportToProvider(null, attendanceMonthlyReport.toDynamicCells(), false);
            }
            //重新设置公式(需要设置分段公式进入表达式引擎)

            resetPhasedFormulaToEngine(hasPhasedAttendanceReport);

            engine.setAutoCalculate(true);
            engine.calculate(true);

            staffPeriodSalary.clearSummarizedData();

            saveCalculatedResult(salaryFieldMap, staffPeriodSalary);

        } catch (Exception e) {
            personalCalcLog.addLog(StepExecutionLog.LogLevel.WARNING, STEP_NAME, e.getMessage() != null ? e.getMessage() : "计算异常");
            personalCalcLog.addLog(StepExecutionLog.LogLevel.TRACE, STEP_NAME, e.getMessage() != null ? e.getMessage() : "计算异常," + e.getClass().getName());
            logger.error(e.getMessage(), e);
        }
    }

    private void printCalcResult(StaffSalaryCalcPeriodTerm term, Map<String, DynamicCell> salaryProfile) {
        if (logger.isDebugEnabled() && salaryProfile != null) {
            Collection<SalaryField> fields = salaryProfile.entrySet().stream().map(s ->
            {
                SalaryField field = new SalaryField();
                field.setCode(s.getKey());
                field.setName(s.getKey());
                return field;
            }).collect(Collectors.toList());
            printCalcResult(new DateRange(term.getStartOn(), term.getEndOn()), engine, fields);
        }
    }

    private void addLastestPeriodToProvider() {
        List<StaffSalaryCalcPeriodTerm> phases = staffPeriodSalary.sortedStaffSalaryCalcPeriodTerms();

        if (CollectionUtils.isNotEmpty(phases)) {
            StaffSalaryCalcPeriodTerm term = phases.get(0);
            clearProflieVariable();
            if (CollectionUtils.isEmpty(term.getItems()) && CollectionUtils.isNotEmpty(profileFields)) {
                Map<String, DynamicCell> map = new HashMap<>(profileFields.size());
                profileFields.stream().forEach(s ->
                                map.put(s, null)
                );
                term.setItems(map);
            }
            addProfileToProvider(term, false);
        }
    }

    private void resetPhasedFormulaToEngine(boolean hasPhasedAttendanceReport) throws ExprException {
        for (SalaryField salaryField : salaryFieldMap.values()) {
            SalaryFieldValue fieldValue = salaryField.getValueMeta();
            FieldSource source = salaryField.getSource();
            if (fieldValue != null) {
                //分段公式
                String phasedFormula = fieldValue.getPhasedFormula();
                //正常公式
                String formula = fieldValue.getFormula();

                String code = salaryField.getCode();

                if (basedOnTermSalary == null && salaryField.isTaxMergeField() && !FormulaFrom.USR.equals(fieldValue)) {
                    // engine.set(code, "0.0");
                    continue;
                }

                //用分段的公式替换默认公式
                if (StringUtils.isNotEmpty(phasedFormula)) {
                    if (!hasPhasedAttendanceReport) {
                        //薪资调整，加班工资，病假扣款，事假扣款，在没有分段考勤数据的情况，采用默认公式而不是分段公式
                        if (code.equals(SalaryFieldCode.OT_SALARY) ||
                                code.equals(SalaryFieldCode.SICK_LEAVE_DEDUCTION) ||
                                code.equals(SalaryFieldCode.UNPAID_LEAVE_DEDUCTION)) {
                            engine.set(code, formula);
                        } else {
                            engine.set(code, phasedFormula);
                        }
                    } else {
                        if ((code.equals(SalaryFieldCode.SICK_LEAVE_DEDUCTION) && !sickLeavePhasedCalcEnable) || (code.equals(SalaryFieldCode.UNPAID_LEAVE_DEDUCTION) && !personalLeavePhasedCalcEnable)) {
                            engine.set(code, formula);
                            continue;
                        }
                        engine.set(code, phasedFormula);
                    }

                } else if (StringUtils.isNotEmpty(formula) && FieldSource.FUNCTION.equals(source)) {
                    //如果是入离职调整，做一些特殊处理
                    if (SalaryFieldCode.ADJUSTMENT_FOR_ATTENDANCE.equals(code)) {
                        if (this.attendanceMonthlyReport == null || this.attendanceMonthlyReport.getFixedPart() == null) {
                            //不需要入离职调整，直接赋值为0
                            engine.set(code, "0.0");
                        } else {
                            double paidDays = this.attendanceMonthlyReport.getFixedPart().getPaiddays();
                            if (paidDays > 0 && paidDays != attendanceLegalPaidDayInThisMonth) {
                                //进行入离职调整
                                if (EnrollAdjustFormula.FORMULA3.equals(enrollAdjustFormula)){
                                    if (this.enrollIn){
                                        engine.set(code, EnrollAdjustFormula.FORMULA3.getFormula());
                                    }else if (this.quitOut){
                                        engine.set(code, QUIT_OUT_FORMULA);
                                    }
                                }else{
                                    engine.set(code, formula);
                                }

                            } else {
                                //不需要入离职调整，直接赋值为0
                                engine.set(code, "0.0");
                            }
                        }

                    } else if (!needPhaseAdjustment && SalaryFieldCode.PHASED_SALARY_ADJUSTMENT.equals(code)) {
                        //如果是不是分段的情况那么，直接设置分段计薪调整为0
                        engine.set(code, "0.0");
                    }
                }
            }
        }
    }

    /**
     * 将合并计算基准数据加入引擎
     */
    private void addBasedOnTermSalaryToProvider() {
        if (this.basedOnTermSalary != null) {
            Map<String, DynamicCell> data = this.basedOnTermSalary.getData();
            if (CollectionUtils.isNotEmpty(data)) {
                for (Map.Entry<String, DynamicCell> entry : data.entrySet()) {
                    String field = entry.getKey();
                    DynamicCell value = entry.getValue();
                    if (value != null) {
                        addCellVariableConvertor(value, VariableID.buildVariableWithSheetIdAndFieldId(SalaryCalcJobConfig.BASED_ON_PLAN_PLACEHOLDER, field));
                    }
                }
            }
        }
    }

    /**
     * 将引用信息加入计算引擎
     */
    private void addReferenceDataToProvider() {
        if (CollectionUtils.isNotEmpty(referenceData)) {
            for (Map.Entry<String, Map<String, Object>> entry : referenceData.entrySet()) {
                String serviceId = entry.getKey();
                Map<String, Object> serviceData = entry.getValue();
                //:TODO 考虑去除未被引用到但是被读取的字段
                //没有根据用户指定的数据类型进行进行赋值，直接根据Object的类型进行转换，可优化
                if (CollectionUtils.isNotEmpty(serviceData)) {
                    for (Map.Entry<String, Object> field : serviceData.entrySet()) {
                        String filedName = field.getKey();
                        Object fieldValue = field.getValue();
                        if (fieldValue != null) {
                            String variableID = VariableID.buildVariableId(serviceId, filedName);

                            //此处的转换应该根据服务应用的类型进行转换,或者在获取服务数据是对获取数据进行转换

                            personVariableProvider.addVaribale(variableID, ExprUtils.convertObject(fieldValue));
                        }
                    }
                }
            }
        }
    }

    /**
     * 讲输入信息加入计算引擎
     */
    private void addInputDataToProvider() {
        if (CollectionUtils.isNotEmpty(inputData)) {
            //只需将工资表中的输入项目加入
            Map<String, DynamicCell> filtedCells = null;
            for (Map.Entry<String, DynamicCell> entry : inputData.entrySet()) {
                String code = entry.getKey();
                if (salaryFieldMap.containsKey(code)) {
                    if (filtedCells == null) {
                        filtedCells = new HashMap<>(inputData.size());
                    }
                    filtedCells.put(code, entry.getValue());
                }
            }
            if (CollectionUtils.isNotEmpty(filtedCells)) {
                addVariables2Provider(null, filtedCells, false);
            }
        }
    }

    /**
     * 将税表信息加入计算引擎
     */
    private void addTaxTableToProvider() {
        if (CollectionUtils.isNotEmpty(taxTables)) {
            EngineProvider provider = engine.getProvider();
            for (Map.Entry<String, ExprArray> taxTableEntry : taxTables.entrySet()) {
                String taxtype = taxTableEntry.getKey();
                ExprArray table = taxTableEntry.getValue();
                provider.addVariable(taxtype, table);
            }
        }
    }

    /**
     * 调整分段计薪调整变量
     *
     * @param phases
     */
    private void processPhaseAdjustmentVariable(List<StaffSalaryCalcPeriodTerm> phases) {
        if (phases.size() > 1) {

            //获取到在薪资周期范围内的分段,处理分段调整，设置分段中的法定工作天数
            List<StaffSalaryCalcPeriodTerm> filteredTerms = phases.stream()
                    .filter(t -> t.getEndOn().after(payrollPriod.getStart()) && t.getStartOn().before(payrollPriod.getEnd()))
                    .map(t ->
                                    new StaffSalaryCalcPeriodTerm(t.getStartOn(), t.getEndOn())
                    )
                    .collect(Collectors.toList());

            StaffSalaryCalcPeriodTerm latestTerm = filteredTerms.get(0);

            if (latestTerm.getEndOn().after(payrollPriod.getEnd())) {
                latestTerm.setEndOn(payrollPriod.getEnd());
            }
            StaffSalaryCalcPeriodTerm fastestTerm = filteredTerms.get(filteredTerms.size() - 1);
            if (fastestTerm.getStartOn().before(payrollPriod.getStart())) {
                fastestTerm.setStartOn(payrollPriod.getStart());
            }
            if (filteredTerms.size() > 1) {
                EngineProvider provider = engine.getProvider();
                needPhaseAdjustment = true;
                int phaseIndex = 0;
                for (StaffSalaryCalcPeriodTerm term : filteredTerms) {
                    int legalPaidDays = workingCalendar.getLegalPaidDayOfThisMonth(term.getStartOn(), term.getEndOn());
                    String phaseName = getPhaseName(false, phaseIndex);
                    provider.addVariable(VariableID.buildPhasedVariableId(phaseName, SalaryFieldCode.LEGAL_PAID_DAYS), new ExprInteger(legalPaidDays));
                    phaseIndex++;
                }
            }
        }
    }

    private void clearProflieVariable() {
        if (CollectionUtils.isNotEmpty(profileFields)) {
            for (String field : profileFields) {
                personVariableProvider.removeVaribale(field);
                engine.getProvider().removeValue(new VariableID(field));
            }
        }
    }

    /**
     * 添加薪资档案数据进入计算引擎
     *
     * @param term
     * @return
     */
    private Map<String, DynamicCell> addProfileToProvider(StaffSalaryCalcPeriodTerm term, boolean ignoreNull) {
        Map<String, DynamicCell> salaryProfile = term.getItems();
        addVariables2Provider(ServiceProvider.SERVICE_ID.STAFF_SALARY, salaryProfile, ignoreNull);
        return salaryProfile;
    }

    /**
     * 添加考勤变量计算引擎
     *
     * @param phaseName
     * @param attendanceData
     * @param isperiod
     * @return
     */
    private Map<String, Double> addAttendanceReportToProvider(String phaseName, Map<String, Double> attendanceData, boolean isperiod) {
        EngineProvider provider = engine.getProvider();
        if (CollectionUtils.isNotEmpty(attendanceData)) {
            for (Map.Entry<String, Double> field : attendanceData.entrySet()) {
                String filedName = field.getKey();
                Double cell = field.getValue();
                if (cell != null) {
                    String variableID = null;
                    if (isperiod) {
                        //分段计算考勤相关薪资使用,分段数据的保存需要进行单独保存
                        variableID = VariableID.buildVariableId(phaseName, ServiceProvider.SERVICE_ID.ATTENDANCE_MONTHLY_REPORT, filedName);
                        provider.addVariable(variableID, new ExprDouble(cell));

                        //本段计算使用
                        variableID = VariableID.buildVariableId(ServiceProvider.SERVICE_ID.ATTENDANCE_MONTHLY_REPORT, filedName);
                        personVariableProvider.addVaribale(variableID, new ExprDouble(cell));
                    } else {
                        variableID = VariableID.buildVariableId(ServiceProvider.SERVICE_ID.ATTENDANCE_MONTHLY_REPORT, filedName);
                        personVariableProvider.addVaribale(variableID, new ExprDouble(cell));
                    }

                }
            }
        }
        return attendanceData;
    }


    private void saveCalculatedResult(Map<String, SalaryField> salaryFieldMap, StaffSalaryCalcPeriod staffPeriodSalary) {
        for (SalaryField field : salaryFieldMap.values()) {
            String code = field.getCode();
            //TODO:根据 salaryfield设定的精度和四舍五入方式保存结果
            String value = engine.getValueAsString(field.toVariableId());
            staffPeriodSalary.addSummarizedItem(code, value);
        }
    }

    /**
     * 将薪资数据加入计算引擎
     *
     * @param isSinglePhase
     * @param salaryProfile
     * @param phaseName
     * @param phaseData
     * @param salaryFieldMap
     */
    private void processProfileSalary(boolean isSinglePhase, Map<String, DynamicCell> salaryProfile, String phaseName, StaffSalaryCalcPeriodTerm phaseData) {
        EngineProvider provider = engine.getProvider();
        for (String salaryProfileKey : salaryProfile.keySet()) {
            SalaryField field = salaryFieldMap.get(salaryProfileKey);
            if (salaryFieldMap.containsKey(salaryProfileKey)) {
                //TODO: 按照salaryfield中配置精度和四舍五入
                String value = engine.getValueAsString(field.toVariableId());
                phaseData.addItem(salaryProfileKey, value);
            }
            //如果是分段计薪，将分段计算的临时结果存入计算引擎中
            if (!isSinglePhase && field != null) {
                Expr exprValue = engine.getValue(field.toVariableId());
                //分段的薪资信息不需要添加 STAFF_SALARY 服务名称，应为对于HR用户而言，他直接感知的是薪资字段，而不是间接引用的薪资档案
                provider.addVariable(VariableID.buildVariableId(phaseName, null, salaryProfileKey), exprValue);
            }
        }

        if (!isSinglePhase) {
            //只有分段时间在薪资周期内，才需要处理薪资分段调整。

            List<StaffSalaryCalcPeriodTerm> termsInRange = staffPeriodSalary.getPhasesInRange(payrollPriod);
            if (CollectionUtils.isEmpty(termsInRange) || termsInRange.size() == 1) {
                //如果考勤周期内没有分段，则直接返回
                return;
            }

            int supposedDays = 0;
            if (phaseData.getStartOn().before(payrollPriod.getStart()) && phaseData.getEndOn().after(payrollPriod.getStart())) {
                //去除薪资周期部分的时间，只计算薪资后期分为内的工作日
                supposedDays = workingCalendar.getWorkingDaysBetween(payrollPriod.getStart(), phaseData.getEndOn());
            } else {
                supposedDays = workingCalendar.getWorkingDaysBetween(phaseData.getStartOn(), phaseData.getEndOn());
            }
            Expr supposedWorkingDays = new ExprInteger(supposedDays);
            provider.addVariable(VariableID.buildVariableId(phaseName, null, SalaryFieldCode.SUPPOSED_WORKING_DAYS), supposedWorkingDays);

        }

    }


    private void processAttendanceSalary(String phaseName, StaffSalaryCalcPeriodTerm phaseData) {
        EngineProvider provider = engine.getProvider();
        SalaryField otsalary = salaryFieldMap.get(SalaryFieldCode.OT_SALARY);
        if (otsalary != null) {
            //TODO: 按照salaryfield中配置精度和四舍五入
            String value = engine.getValueAsString(otsalary.toVariableId());
            phaseData.addItem(SalaryFieldCode.OT_SALARY, value);
            Expr exprValue = engine.getValue(otsalary.toVariableId());
            provider.addVariable(VariableID.buildPhasedVariableId(phaseName, SalaryFieldCode.OT_SALARY), exprValue);
        }
        SalaryField sickleave = salaryFieldMap.get(SalaryFieldCode.SICK_LEAVE_DEDUCTION);
        if (sickleave != null) {
            //TODO: 按照salaryfield中配置精度和四舍五入
            String value = engine.getValueAsString(sickleave.toVariableId());
            phaseData.addItem(SalaryFieldCode.SICK_LEAVE_DEDUCTION, value);
            Expr exprValue = engine.getValue(sickleave.toVariableId());
            provider.addVariable(VariableID.buildPhasedVariableId(phaseName, SalaryFieldCode.SICK_LEAVE_DEDUCTION), exprValue);
        }
        SalaryField unpaidleave = salaryFieldMap.get(SalaryFieldCode.UNPAID_LEAVE_DEDUCTION);
        if (unpaidleave != null) {
            //TODO: 按照salaryfield中配置精度和四舍五入
            String value = engine.getValueAsString(unpaidleave.toVariableId());
            phaseData.addItem(SalaryFieldCode.UNPAID_LEAVE_DEDUCTION, value);
            Expr exprValue = engine.getValue(unpaidleave.toVariableId());
            provider.addVariable(VariableID.buildPhasedVariableId(phaseName, SalaryFieldCode.UNPAID_LEAVE_DEDUCTION), exprValue);
        }
        for (Map.Entry<String, SalaryField> entry : attendanceSalaryFieldMap.entrySet()) {
            SalaryField value = entry.getValue();
            SalaryFieldType type = value.getType();
            if (type != null) {
                //将考勤部分数据加入分段你数据中
                if (SalaryFieldType.Code.attendance.equals(type.getCode())) {
                    String strValue = engine.getValueAsString(VariableID.valueOf(entry.getKey()));
                    phaseData.addItem(entry.getKey(), strValue);
                }
            }
        }
    }

    /**
     * 获取分段计算部分的前缀
     *
     * @param isSingle
     * @param phase
     * @return
     */
    private String getPhaseName(Boolean isSingle, int phase) {
        if (isSingle) {
            return null;
        }
        return SalaryPhase.toPhase(phase).toString();
    }

    /**
     * 考勤周期和薪资周期不一致
     * case1
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +       (转正)
     * +------A--+---B---1-------------C-------------------------------31
     * (加班)             (加班)
     * +26------28-------1------------------------------------25
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * <p>
     * * case2
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +                              (转正)
     * +----------A-----1--------B------+-----------C------------------31
     * (加班)             (加班)           (加班)
     * +26--------------1------------------------------------25
     * +
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * <p>
     */
    private StaffSalaryCalcPeriod processMultiplePhasesWithDifferentPeriod() {
        return null;
    }
}